/*
 ============================================================================
 Name        : com.cpp
 Author      : Tymoteusz Blazejczyk (tymon_x) tymoteusz.blazejczyk.pl@gmail.com
 Version     : 0.1a
 Description : Serial Communication (Low-End)
 License     : BSD New

 Copyright (c) 2013, Tymoteusz Blazejczyk (tymoteusz.blazejczyk.pl@gmail.com)
 All rights reserved.

 Redistribution and use in source and binary forms, with or without modification,
 are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation and/or other
   materials provided with the distribution.
 * Neither the name of the tymon_x nor the names of its contributors may be used to endorse or
   promote products derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ============================================================================
 */

#include "com.h"
#include <QtCore>

#ifdef Q_WS_WIN
#include <windows.h>
#endif
#ifdef Q_WS_X11
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <stdio.h>
#endif

//---------------------------------------------------------------------------


Com::Com()
{
#ifdef Q_OS_WIN32
        this->cbInQueue=512;
        this->cbOutQueue=512;
        this->BaudRate=CBR_9600;
        this->hCommDev=NULL;
#endif
#ifdef Q_OS_LINUX
        this->cbInQueue=4096;
        this->cbOutQueue=1024;
        this->BaudRate=9600;
        this->fd=-1;
#endif
}


bool Com::open(const char *CommID, const unsigned long Baud)
{
    qDebug() << __FUNCTION__ << "magcon.open()";
#ifdef Q_OS_WIN32
    DCB     dcb;
    COMMTIMEOUTS commTimeouts;
    WCHAR wPort[255] = {0};

    this->BaudRate=Baud;

    if (strlen(CommID) >= 255)
    {
        qDebug() << "Error length";
        return false;
    }
    for(unsigned int i=0; i<strlen(CommID); i++)
    {
        wPort[i]   = CommID[i];
        wPort[i+1] = 0;
    }

    hCommDev = CreateFile( wPort,
                           GENERIC_READ | GENERIC_WRITE,
                           0, NULL,
                           OPEN_EXISTING, 0, NULL);

    if (hCommDev != INVALID_HANDLE_VALUE)
    {
        dcb.DCBlength = sizeof(dcb);
        if(!GetCommState(hCommDev, &dcb))
        {
            qDebug() << "Error GetCommState";
            return false;
        }
        dcb.BaudRate=this->BaudRate;

        //transmission parameters
        dcb.Parity = NOPARITY;
        dcb.StopBits = ONESTOPBIT;
        dcb.ByteSize = 8;

        //DCB flags
        dcb.fParity = FALSE;
        dcb.fDtrControl = DTR_CONTROL_DISABLE;

        //DTR line disabled
        dcb.fRtsControl = RTS_CONTROL_DISABLE;

        //RTS line disabled
        dcb.fOutxCtsFlow = FALSE;
        dcb.fOutxDsrFlow = FALSE;
        dcb.fDsrSensitivity = FALSE;
        dcb.fAbortOnError = FALSE;
        dcb.fOutX = FALSE;
        dcb.fInX = FALSE;
        dcb.fErrorChar = FALSE;
        dcb.fNull = FALSE;

        if(!SetCommState(hCommDev,&dcb))
        {
            qDebug() << "Error SetCommState";
            return false;
        }

        if(!SetupComm(hCommDev, cbInQueue, cbOutQueue))
        {
            qDebug() << "Error SetupComm";
            return false;
        }
        GetCommTimeouts(hCommDev, &commTimeouts);
        commTimeouts.ReadIntervalTimeout = 40;
        commTimeouts.ReadTotalTimeoutMultiplier = 40;
        commTimeouts.ReadTotalTimeoutConstant = 200;
        if(!SetCommTimeouts(hCommDev, &commTimeouts))
        {
            qDebug() << "Error SetCommTimeouts";
            return false;
        }
    }
    else
    {
        qDebug() << "Error CreateFile " << (char)wPort[0] << (char)wPort[1] << (char)wPort[2] << (char)wPort[3] << (char)wPort[4] << (char)wPort[5];
        return false;
    }
    return true;
#endif
#ifdef Q_OS_LINUX
    //make sure port is closed
    close();

    this->BaudRate=Baud;
    fd=::open(CommID, O_RDWR | O_NOCTTY | O_NDELAY | O_NDELAY);
    if (fd<0)
    {
        qDebug() << "Error open port";
        return false;
    }
    else
    {
        struct termios options;

        fcntl(fd, F_SETFL, 0);

        tcgetattr(fd, &options);

        switch(Baud)
        {
                case 9600:
                        cfsetispeed(&options, B9600);
                        cfsetospeed(&options, B9600);
                        break;
                case 19200:
                        cfsetispeed(&options, B19200);
                        cfsetospeed(&options, B19200);
                        break;
                case 38400:
                        cfsetispeed(&options, B38400);
                        cfsetospeed(&options, B38400);
                        break;
                case 57600:
                        cfsetispeed(&options, B57600);
                        cfsetospeed(&options, B57600);
                        break;
                case 115200:
                        cfsetispeed(&options, B115200);
                        cfsetospeed(&options, B115200);
                        break;
                default:
                        cfsetispeed(&options, B300);
                        cfsetospeed(&options, B300);
                        break;
        }

        //8N1 frame
        options.c_cflag &= ~PARENB;
        options.c_cflag &= ~CSTOPB;
        options.c_cflag &= ~CSIZE;
        options.c_cflag |= CS8;

        //disable hardware flow control
        options.c_cflag &= ~CRTSCTS;

        //Enable receiver and local mode
        options.c_cflag |= (CLOCAL | CREAD);

        //Raw input
        options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

        //Disable parity checking
        options.c_iflag &= ~(INPCK | ISTRIP);

        //Don't map CR to NL or NL to CR
        options.c_iflag &= ~(ICRNL | INLCR);

        //Don't map uppercase to lowercase
        options.c_iflag &= ~IUCLC;

        //Don't ignore CR
        options.c_iflag &= ~IGNCR;

        //Ignore BREAK condition
        options.c_iflag |= IGNBRK;

        //Disable software flow control
        options.c_iflag &= ~(IXON | IXOFF | IXANY);

        //Raw output - other c_oflag bits ignored
        options.c_oflag &= ~OPOST;

        options.c_cc[VMIN]=0;
        options.c_cc[VTIME]=0;

        //Set new settings
        tcsetattr(fd, TCSANOW, &options);
    }
    return true;
#endif
}


bool Com::close(void)
{
    qDebug() << __FUNCTION__ << "magcon.close()";
#ifdef Q_OS_WIN32
    if((hCommDev != INVALID_HANDLE_VALUE) && (hCommDev > 0))
        return CloseHandle(hCommDev);
    else
        return false;
#endif
#ifdef Q_OS_LINUX
    if(fd<=0)
        return false;

    if(::close(fd)<0)
        return false;
    else
        return true;
#endif
}


unsigned long Com::write(const void* Buffer, unsigned long NumberOfBytesToWrite)
{
#ifdef Q_OS_WIN32
    unsigned long NumberOfBytesWritten;
    if(WriteFile(hCommDev,Buffer,NumberOfBytesToWrite, &NumberOfBytesWritten, NULL)==false)
    {
        return 0;
    }
    return NumberOfBytesWritten;
#endif
#ifdef Q_OS_LINUX
    int iOutWrite;
    if(fd<1)
    {
        return 0;
    }
    iOutWrite = ::write(fd, Buffer, NumberOfBytesToWrite);
    if(iOutWrite<0)
    {
        return 0;
    }
    return iOutWrite;
#endif
}


bool Com::read(void * Buffer, unsigned long * lpNumberOfBytesRead, unsigned long NumberOfBytesToRead)
{
#ifdef Q_OS_WIN32
    unsigned long nNumberOfBytesToRead;

    if (NumberOfBytesToRead > this->cbInQueue)
    {
        nNumberOfBytesToRead=this->cbInQueue;
    }
    else
    {
        nNumberOfBytesToRead=NumberOfBytesToRead;
    }
    return ReadFile(hCommDev,Buffer,nNumberOfBytesToRead,lpNumberOfBytesRead, NULL);
#endif
#ifdef Q_OS_LINUX
    unsigned long nNumberOfBytesToRead;

    if (NumberOfBytesToRead>this->cbInQueue)
        nNumberOfBytesToRead=this->cbInQueue;
    else
        nNumberOfBytesToRead=NumberOfBytesToRead;

    if(fd<1)
    {
        return false;
    }
    int iInRead=::read(fd, Buffer, nNumberOfBytesToRead);
    if(iInRead<0)
    {
        return false;
    }
    else
    {
        *lpNumberOfBytesRead=iInRead;
    }
    return true;
#endif
}


bool Com::checkCommInput(unsigned long * lpErrors, unsigned long * lpInQue)
{
#ifdef Q_OS_WIN32
    COMSTAT Stat;
    if(false==ClearCommError(hCommDev,lpErrors,&Stat))
    {
        PurgeComm(hCommDev,PURGE_RXCLEAR|PURGE_TXCLEAR);
        return false;
    }
    *lpInQue=Stat.cbInQue;
    return true;
#endif
#ifdef Q_OS_LINUX
    *lpErrors=0;
    if(ioctl(fd, FIONREAD, lpInQue)<0)
    {
        return false;
    }
    return true;
#endif
}

